package nio.communication;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * NIO 非阻塞 网络编程快速入门
 * 客户端
 *
 * @author chenyuan
 * @date 2020/11/4 9:31
 */
public class NIOServer {
	public static void main(String[] args) throws Exception {
		// 创建 ServerSocketChannel
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		// 得到一个 Selector 实例
		Selector selector = Selector.open();
		// 绑定端口，在服务端进行监听
		serverSocketChannel.socket().bind(new InetSocketAddress(6666));
		// 设置为非阻塞
		serverSocketChannel.configureBlocking(false);
		// 把 ServerSocketChannel 注册到 Selector 关心事件为 OP_ACCEPT(连接)
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		// 循环等待客户端连接
		while (true) {
			// 等待一秒，如果没有事件发生，就继续
			if (selector.select(1000) == 0) {
				System.out.println("服务器等待了 1 s，无连接");
				continue;
			}
			// 如果有事件发生，获取到发生事件的 SelectionKey 集合
			Set<SelectionKey> selectionKeys = selector.selectedKeys();
			// 通过 SelectionKey 反向获取对应通道
			Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
			while (keyIterator.hasNext()) {
				// 获取 keyIterator
				SelectionKey key = keyIterator.next();
				// 根据 key 发生的事件，做相应的处理
				// 如果是连接的事件
				if (key.isAcceptable()) {
					// 通过 serverSocketChannel 给该客户端生成一个 SocketChannel
					SocketChannel socketChannel = serverSocketChannel.accept();
					// 设置为非阻塞
					socketChannel.configureBlocking(false);
					// 将当前的 socketChannel 注册到 Selector，关心事件为 OP_READ（读），
					// 同时给 socketChannel 关联一个 Buffer
					socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
				}
				// 如果是读事件
				if (key.isReadable()) {
					// 通过 key 反向获取对应的 Channel
					SocketChannel channel = (SocketChannel) key.channel();
					// 获取该 SocketChannel 关联的 Buffer
					ByteBuffer buffer = (ByteBuffer) key.attachment();
					// 把 Channel 中的数据读入到 Buffer 中
					channel.read(buffer);
					System.out.println("from 客户端——" + new String(buffer.array()));
				}
				// 处理完毕后要手动删除当前的 SelectionKey，避免多线程重复操作
				keyIterator.remove();
			}

		}
	}
}
